import base64
import socket
import struct
import time
import queue
import threading
import logging

import pyautogui
import PIL.ImageGrab
from io import BytesIO
import configparser
from PIL import Image
from PyQt5.QtGui import QImage, QPixmap
from ultralytics import YOLO
import cv2
import numpy as np
from PyQt5.QtCore import Qt, pyqtSignal, QThread

from game import Game

# 设置日志配置
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

# 创建文件处理器
file_handler = logging.FileHandler('game_log.txt')
file_handler.setLevel(logging.DEBUG)

# 创建日志格式
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
file_handler.setFormatter(formatter)

# 添加处理器到logger
logger.addHandler(file_handler)


class Task:
    """任务类，用于封装任务类型和参数"""

    def __init__(self, task_type, client_id, **kwargs):
        self.task_type = task_type  # 'click' 或 'keyboard'
        self.client_id = client_id  # 目标客户端的编号
        self.kwargs = kwargs  # 任务的具体参数，如点击坐标或键盘按键


class ClientHandler(QThread):
    update_screen = pyqtSignal(QPixmap, QPixmap, QPixmap, QPixmap)  # 用于更新UI的信号
    log_message = pyqtSignal(str)  # 用于日志输出的信号
    tmp_check_times = 0
    success_check_times = 0

    def __init__(self, conn, addr, model):
        super().__init__()
        self.conn = conn
        self.addr = addr
        self.model = model
        names = {0: '验证_倒计时', 1: '验证_验证图', 2: '文字_向左转动', 3: '文字_向右转动', 4: '验证_示例图',
                 5: '文字_验证通过', 6: '玩家_正常形态', 7: '玩家_侏儒形态', 8: '按钮_问号'}
        self.game = Game(names)

    def run(self):
        self.send_screenshot()
        self.log_message.emit(f"正在处理客户端 {self.addr}")
        try:
            while True:
                header = self.conn.recv(1)
                if not header:
                    break

                message_type = struct.unpack('>B', header)[0]
                if message_type == 0xF1:  # 处理截图指令
                    print("点击事件完成")
                elif message_type == 0xF2:  # 发送截图指令
                    self.handle_screenshot()
                    time.sleep(0.1)
                    self.send_screenshot()
                elif message_type == 0xF3:
                    print("键盘点击事件完成")
                else:
                    self.log_message.emit(f"未知的消息类型: {message_type}")
        except Exception as ex:
            self.log_message.emit(f"处理客户端 {self.addr} 时出错: {ex}")
        finally:
            self.conn.close()
            self.log_message.emit(f"与客户端 {self.addr} 的连接已关闭")

    def handle_screenshot(self):
        """处理客户端发送的截图数据"""
        try:
            raw_data_length = self.conn.recv(4)
            data_length = struct.unpack('>I', raw_data_length)[0]

            screenshot_data = b''
            while len(screenshot_data) < data_length:
                packet = self.conn.recv(data_length - len(screenshot_data))
                if not packet:
                    break
                screenshot_data += packet

            if len(screenshot_data) != data_length:
                self.log_message.emit("接收到的截图数据不完整")
                return

            self.log_message.emit(f"从客户端 {self.addr} 收到截图")
            self.analyze_screenshot(screenshot_data)
        except Exception as e:
            self.log_message.emit(f"处理截图时出错: {e}")

    def analyze_screenshot(self, screenshot_data):
        """分析截图并发送点击指令"""
        try:
            # 将二进制数据加载为 QPixmap
            screen_pixmap = QPixmap()
            yolo_pixmap = QPixmap()
            num_pixmap = QPixmap()
            icon_pixmap = QPixmap()
            target_icon_pixmap = QPixmap()
            screen_pixmap.loadFromData(screenshot_data)

            # 将 QPixmap 转换为 QImage
            screen_image = screen_pixmap.toImage()

            # 将 QImage 转换为 OpenCV 格式 (numpy array)
            width = screen_image.width()
            height = screen_image.height()
            ptr = screen_image.bits()
            ptr.setsize(screen_image.byteCount())
            image_cv = np.array(ptr).reshape(height, width, 4)  # 转换为 RGBA 格式
            image_cv = cv2.cvtColor(image_cv, cv2.COLOR_RGBA2BGR)  # 转换为 BGR 格式

            # 使用模型处理图像
            results = self.model(image_cv)

            pos_x, pos_y = 0, 0
            if len(results) > 0:
                cls = list(results[0].boxes.cls)
                if 0 in cls:
                    n = cls.index(0)
                    xyxy = results[0].boxes.xyxy[n]
                    x0, y0, x1, y1 = map(int, map(round, xyxy.cpu().numpy()))
                    # 截取指定区域的 QPixmap
                    num_pixmap = screen_pixmap.copy(x0, y0, x1 - x0, y1 - y0)

                if 1 in cls:
                    n = cls.index(1)
                    xyxy = results[0].boxes.xyxy[n]
                    x0, y0, x1, y1 = map(int, map(round, xyxy.cpu().numpy()))
                    # 截取指定区域的 QPixmap
                    target_icon_pixmap = screen_pixmap.copy(x0, y0, x1 - x0, y1 - y0)

                if 4 in cls:
                    n = cls.index(4)
                    xyxy = results[0].boxes.xyxy[n]
                    x0, y0, x1, y1 = map(int, map(round, xyxy.cpu().numpy()))
                    # 截取指定区域的 QPixmap
                    icon_pixmap = screen_pixmap.copy(x0, y0, x1 - x0, y1 - y0)

                # 绘制检测结果
                for result in results:
                    boxes = result.boxes
                    for box in boxes:
                        x1, y1, x2, y2 = map(int, map(round, box.xyxy[0].cpu().numpy()))
                        label = int(box.cls[0].cpu().numpy())
                        conf = box.conf[0].cpu().numpy()

                        if conf > 0.5:
                            cv2.rectangle(image_cv, (x1, y1), (x2, y2), (0, 255, 0), 2)
                            cv2.putText(image_cv, f'Class: {label}, Conf: {conf:.2f}', (x1, y1 - 10),
                                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

                # 将 OpenCV 图像转换回 QPixmap
                img = cv2.cvtColor(image_cv, cv2.COLOR_BGR2RGB)
                height, width, channels = img.shape
                bytes_per_line = channels * width
                yolo_pixmap = QPixmap.fromImage(
                    QImage(img.data, width, height, bytes_per_line, QImage.Format_RGB888)
                )
                if not num_pixmap.isNull():
                    # 分析是否验证中
                    checking, recognized_results = self.game.analyze(Game.pixmap_to_pil_image(num_pixmap))

                    # 如果正在验证，则手动过验证
                    if checking:
                        # 如果存在 文字_向左转动
                        go_left, pos_x, pos_y = self.game.btn_pos("文字_向左转动", results[0].boxes)
                        if not go_left:
                            self.game.notify("checking")
                            self.tmp_check_times = 0
                            go_help, pos_x, pos_y = self.game.btn_pos("按钮_问号", results[0].boxes)  # 假设检测到的点击位置
                        else:
                            self.log_message.emit(f"自动验证提醒autocheck_begin")
                            self.game.notify("autocheck_begin")
                            # 计算次数
                            icon_img = Game.pixmap_to_pil_image(icon_pixmap)
                            target_icon_img = Game.pixmap_to_pil_image(target_icon_pixmap)
                            direction, best_angle, steps, best_rotated_icon, best_match_value = self.game.find(icon_img,
                                                                                                               target_icon_img)
                            self.log_message.emit(f"自动验证方向: 向 '{direction}' -> '{steps}' 次.")
                            # 点击向左或者向右
                            go_turn, pos_x, pos_y = self.game.btn_pos("文字_向左转动", results[
                                0].boxes) if direction == '左' else self.game.btn_pos("文字_向右转动", results[0].boxes)
                            self.tmp_check_times = self.tmp_check_times + 1
                            # 判断是否成功
                            if steps == 0:
                                go_help, pos_x, pos_y = self.game.btn_pos("文字_验证通过", results[0].boxes)
                                self.game.notify("autocheck_success")
                                self.log_message.emit(f"☆★验证失败{self.tmp_check_times}次，自动验证成功。★☆")
                            else:
                                self.game.notify("autocheck_fail")
                                self.log_message.emit(f"☆★验证失败{self.tmp_check_times}次，自动验证继续。★☆")
                            time.sleep(0.3)
            else:
                yolo_pixmap = screen_pixmap.copy()
            self.update_screen.emit(yolo_pixmap, num_pixmap, icon_pixmap, target_icon_pixmap)
            self.send_click(pos_x, pos_y)
            time.sleep(0.5)
        except Exception as e:
            self.log_message.emit(f"分析截图时出错: {e}")

    def send_click(self, x, y):
        """发送点击指令"""
        try:
            message_type = 0x01
            packed_data = struct.pack('>BII', message_type, x, y)
            self.conn.sendall(packed_data)
            self.log_message.emit(f"向客户端 {self.addr} 发送点击指令: ({x}, {y})")
        except Exception as e:
            self.log_message.emit(f"发送点击指令时出错: {e}")

    def send_keyboard(self, key):
        """发送键盘指令"""
        try:
            message_type = 0x03  # 键盘指令的消息类型
            key_bytes = key.encode('utf-8')  # 将按键转换为字节
            key_length = len(key_bytes)
            packed_data = struct.pack(f'>BI{key_length}s', message_type, key_length, key_bytes)
            self.conn.sendall(packed_data)
            self.log_message.emit(f"向客户端 {self.addr} 发送键盘指令: {key}")
        except Exception as e:
            self.log_message.emit(f"发送键盘指令时出错: {e}")

    def send_screenshot(self):
        """发送截图指令"""
        try:
            message_type = 0x02
            packed_data = struct.pack('>B', message_type)
            self.conn.sendall(packed_data)
            self.log_message.emit(f"向客户端 {self.addr} 发送截图指令")
        except Exception as e:
            self.log_message.emit(f"发送截图指令时出错: {e}")


class CheckingServer(QThread):
    update_screen = pyqtSignal(QPixmap, QPixmap, QPixmap, QPixmap)  # 用于更新UI的信号
    log_message = pyqtSignal(str)  # 用于日志输出的信号

    def __init__(self, host='0.0.0.0', port=2055):
        super().__init__()
        self.host = host
        self.port = port
        self.socket = self._create_socket()
        self.model = YOLO("best.pt")
        self.task_queue = queue.Queue()  # 任务队列
        self.clients = {}  # 存储客户端连接的字典，key为客户端编号，value为ClientHandler实例
        self.client_counter = 0  # 客户端编号计数器
        self.lock = threading.Lock()  # 用于线程安全的锁
        self.running = True  # 控制线程运行的标志

        # 启动任务处理线程
        self.task_thread = threading.Thread(target=self.process_tasks)
        self.task_thread.start()

    def _create_socket(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind((self.host, self.port))
        sock.listen(5)
        print(f"服务器正在监听 {self.host}:{self.port}")
        self.log_message.emit(f"服务器正在监听 {self.host}:{self.port}")
        return sock

    def run(self):
        while self.running:
            conn, addr = self.socket.accept()
            self.log_message.emit(f"接受来自 {addr} 的连接")
            with self.lock:
                self.client_counter += 1
                client_id = self.client_counter
                print(f"接受来自{client_id} -> {addr} 的连接")
                client_handler = ClientHandler(conn, addr, self.model)
                if client_id == 1:
                    client_handler.update_screen.connect(self.update_screen)
                    client_handler.log_message.connect(self.log_message)
                self.clients[client_id] = client_handler
                client_handler.start()

    def add_task(self, task):
        """添加任务到队列"""
        self.task_queue.put(task)

    def process_tasks(self):
        """处理任务队列中的任务"""
        while self.running:
            try:
                task = self.task_queue.get(timeout=1)  # 从队列中获取任务，超时1秒
                if task.task_type == 'click':
                    self.handle_click_task(task)
                elif task.task_type == 'keyboard':
                    self.handle_keyboard_task(task)
            except queue.Empty:
                continue

    def handle_click_task(self, task):
        """处理点击任务"""
        with self.lock:
            client_handler = self.clients.get(task.client_id)
            if client_handler:
                x = task.kwargs.get('x', 0)
                y = task.kwargs.get('y', 0)
                client_handler.send_click(x, y)
            else:
                self.log_message.emit(f"客户端 {task.client_id} 不存在")

    def handle_keyboard_task(self, task):
        """处理键盘任务"""
        with self.lock:
            client_handler = self.clients.get(task.client_id)
            if client_handler:
                key = task.kwargs.get('key', '')
                client_handler.send_keyboard(key)
                # 这里可以实现发送键盘指令的逻辑
                self.log_message.emit(f"向客户端 {task.client_id} 发送键盘指令: {key}")
            else:
                self.log_message.emit(f"客户端 {task.client_id} 不存在")

    def stop(self):
        """停止服务器"""
        self.running = False
        self.task_thread.join()
        self.socket.close()
        self.log_message.emit("服务器已停止")

# 示例用法
# if __name__ == "__main__":
#     server = CheckingServer()
#     server.start()
#
#     # 添加任务
#     click_task = Task(task_type='click', client_id=1, x=100, y=200)
#     server.add_task(click_task)
#
#     keyboard_task = Task(task_type='keyboard', client_id=2, key='A')
#     server.add_task(keyboard_task)
#
#     # 停止服务器
#     time.sleep(10)  # 模拟运行一段时间
#     server.stop()
